<?php

namespace app\common;
/**
 * Class RSA
 * @package app\common
 * PHP使用openssl进行Rsa加密，如果要加密的明文太长则会出错，解决方法：加密的时候117个字符加密一次，然后把所有的密文拼接成一个密文；解密的时候需要128个字符解密一下，然后拼接成数据
 * RSA加密解密有个填充方式padding的参数，不同编程语言之间交互，需要注意这个。
 * padding can be one of OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING,OPENSSL_NO_PADDING
 * 值得注意的是，如果选择密钥是1024bit长的（openssl genrsa -out rsa_private_key.pem 1024），那么支持加密的明文长度字节最多只能是1024/8=128byte；
 * 如果加密的padding填充方式选择的是OPENSSL_PKCS1_PADDING（这个要占用11个字节），那么明文长度最多只能就是128-11=117字节。如果超出，那么这些openssl加解密函数会返回false。
 * 这时有个解决办法，把需要加密的源字符串按少于117个长度分开为几组，在解密的时候以172个字节分为几组。
 * 其中的『少于117』（只要不大于117即可）和『172』两个数字是怎么来的，值得一说。
 * 为什么少于117就行，因为rsa encrypt后的字节长度是固定的，就是密钥长1024bit/8=128byte。因此只要encrypt不返回false，即只要不大于117个字节，那么返回加密后的都是128byte。
 * 172是因为什么？因为128个字节base64_encode后的长度固定是172。
 * 这里顺便普及下base64_encode。encode的长度是和原文长度有个计算公式：
 * $len2 = $len1%3 >0 ? (floor($len1/3)*4 + 4) : ($len1*4/3);
 *
 * 为什么RSA公钥每次加密得到的结果都不一样？
 * https://blog.csdn.net/guyongqiangx/article/details/74930951

 */
class RSA
{
    public $private_key = '-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALwUhjPVnJvvxBIK
MN5/HrGlxhB1SyHHO+6jG7nI9DgyObgRK96BOOsRpk4Il4fGfMC0KwVCGs9jRPuX
rthXvETGQaHTQ8tRYaPV5GNlUp26y2YwedOAF1EGUhXddd7Z7lnNfg8exwX9/8ag
AiX+3WVBkjQn/+Xw5+vqvlkETT2FAgMBAAECgYAL48REHJnk5bhgZ5KFTkXzdwZZ
XgDc5spsIJWNHh4KO2qYr0U1sLeXbKs1vadOpwaV7A3YLECjoaPnv8QG2y0ElEGj
wQNNGKpt6LSd1jeOk5ZfX/JzyGIwCH1WSiNCRFkjCCXI1MhLBGSWMgK9anKLOFKO
PggR2+lXUwFuuSoudQJBANv+zGKzOPo5qG6bNmnDHj/5JEfVNHF3V1ei+8LoU7yE
3BVPCyLSs1V3N5Bhr0S6CsidWxvlzdvzJTOO3KfbScsCQQDa3JGtLaM2IJfpYpmf
GM9tqbmUAAbgSJO/SGtEiXCxUJDbanW000bv/P+bdrqADpy4fc20B8+KWL1NSnGC
wuvvAkEAq57MZzsVee88/HxNKJi0ooeDzB4BOXs2QgG9kcGYgXrMDjuJnnLswAGu
8Q2jo2PXQcWv68WMoMe//8a1UKV7nQJAd3KaLyIRSaFQR5gAr8wiqX9QQW4iUpZJ
zMQBh05bquqFpLrdsur8dE5oUrjh4LIY5rgCZJc+tgrohh5zBSLNEQJADfGYthkB
f9g9veSwI0/ej6t0nF7D0kzKwbyls9IMZaWpc2AnXRuG4RZifcoKoiQo2v0B5hY8
KmyvSkn+lS4etQ==
-----END PRIVATE KEY-----
';
    //公钥
    public $public_key = '-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8FIYz1Zyb78QSCjDefx6xpcYQ
dUshxzvuoxu5yPQ4Mjm4ESvegTjrEaZOCJeHxnzAtCsFQhrPY0T7l67YV7xExkGh
00PLUWGj1eRjZVKdustmMHnTgBdRBlIV3XXe2e5ZzX4PHscF/f/GoAIl/t1lQZI0
J//l8Ofr6r5ZBE09hQIDAQAB
-----END PUBLIC KEY-----';


    public $pi_key;
    public $pu_key;

    //判断公钥和私钥是否可用
    public function __construct()
    {
        $this->pi_key = openssl_pkey_get_private($this->private_key);//这个函数可用来判断私钥是否是可用的，可用返回资源id Resource id
        $this->pu_key = openssl_pkey_get_public($this->public_key);//这个函数可用来判断公钥是否是可用的
        //   print_r($this->pi_key);echo "\n";
        //   print_r($this->pu_key);echo "\n";
    }

    //私钥加密
    /*public function PrivateEncrypt($data){
        openssl_private_encrypt($data,$encrypted,$this->pi_key);
        $encrypted = $this->urlsafe_b64encode($encrypted);//加密后的内容通常含有特殊字符，需要编码转换下，在网络间通过url传输时要注意base64编码是否是url安全的
        return $encrypted;
    }*/

    public function privateEncrypt($data)
    {
        // openssl_private_encrypt($data,$encrypted,$this->pi_key);
        $crypto = '';
        foreach (str_split($data, 117) as $chunk) {
            openssl_private_encrypt($chunk, $encryptData, $this->pi_key);
            $crypto .= $encryptData;
        }
        $encrypted = $this->urlsafe_b64encode($crypto);//加密后的内容通常含有特殊字符，需要编码转换下，在网络间通过url传输时要注意base64编码是否是url安全的
        return $encrypted;
    }


    //加密码时把特殊符号替换成URL可以带的内容
    function urlsafe_b64encode($string)
    {
        $data = base64_encode($string);
        $data = str_replace(array('/', '+'), array('_', '-'), $data);
        return $data;
    }

    //解密码时把转换后的符号替换特殊符号
    function urlsafe_b64decode($string)
    {

        $data = str_replace(array('_', '-'), array('/', '+'), $string);
        //+会转成%
        $mod4 = strlen($data) % 4;
        if ($mod4) {
            $data .= substr('====', $mod4);
        }

        return base64_decode($data);
    }

    //私钥加密的内容通过公钥可用解密出来
    public function publicDecrypt($encrypted)
    {

        // $encrypted = $this->urlsafe_b64decode($encrypted);
        $crypto = '';
        foreach (str_split($this->urlsafe_b64decode($encrypted), 128) as $chunk) {

            openssl_public_decrypt($chunk, $decryptData, $this->pu_key);
            $crypto .= $decryptData;
        }
        //openssl_public_decrypt($encrypted,$decrypted,$this->pu_key);//私钥加密的内容通过公钥可用解密出来
        return json_decode($crypto, true);
    }

    //公钥加密
    public function publicEncrypt($data)
    {
        //openssl_public_encrypt($data,$encrypted,$this->pu_key);//公钥加密
        $crypto = '';
        foreach (str_split($data, 117) as $chunk) {
            openssl_public_encrypt($chunk, $encryptData, $this->pu_key);
            $crypto .= $encryptData;
        }
        $encrypted = $this->urlsafe_b64encode($crypto);
        return ($encrypted);
    }

    //私钥解密
    public function privateDecrypt($encrypted)
    {
        $crypto = '';

        foreach (str_split($this->urlsafe_b64decode($encrypted), 128) as $chunk) {
            openssl_private_decrypt($chunk, $decryptData, $this->pi_key);
            $crypto .= $decryptData;
        }
        //$encrypted = $this->urlsafe_b64decode($encrypted);
        //openssl_private_decrypt($encrypted,$decrypted,$this->pi_key);

        return json_decode($crypto, true);
    }

    public function sign($data, $code = 'base64')
    {
        $sign = '';
        openssl_sign($data, $sign, $this->pi_key);
        openssl_free_key($this->pi_key);
        $sign = base64_encode($sign);//
        return $sign;
    }

    function checkSign($sign, $toSign)
    {
        $publicKeyId = openssl_pkey_get_public($this->pu_key);
        $result = openssl_verify($toSign, base64_decode($sign), $publicKeyId);
        openssl_free_key($publicKeyId);
        return $result === 1 ? true : false;
    }

    /**
     * 获取待签名字符串
     * @param    array $params 参数数组
     * @return   string
     */
    function getSignString($params)
    {
        unset($params['time']);
        ksort($params);
        reset($params);

        $pairs = array();
        foreach ($params as $k => $v) {
            if (!empty($v)) {
                $pairs[] = "$k=$v";
            }
        }

        return implode('&', $pairs);
    }


}
